﻿namespace FlotDotNet
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Globalization;
    using System.ComponentModel;

    /// <summary>
    /// A wrapper around the Javascript plotting library Flot which produces graphical plots of arbitrary datasets on-the-fly client-side.
    /// </summary>
    public class FlotChart : FlotElementBase
    {
        private List<FlotSeries> mSeries;
        private FlotLegend mLegend;
        private FlotGrid mGrid;
        private FlotAxis mXAxis;
        private FlotAxis mYAxis;
        private List<FlotAxis> mXAxes;
        private List<FlotAxis> mYAxes;
        private List<FlotColor> mColors;


        private const string AttributeFormat = @" {0}=""{1}""";
        private const string ElementFormatNormal = "<{0}{1}>{2}</{0}>";

        /// <summary>
        /// Gets the x axis of the chart
        /// </summary>
        public FlotAxis XAxis
        {
            get
            {
                if (mXAxis == null)
                    mXAxis = new FlotAxis("xaxis");
                return mXAxis;
            }
        }
        /// <summary>
        /// Gets the y axis of the chart
        /// </summary>
        public FlotAxis YAxis
        {
            get
            {
                if (mYAxis == null)
                    mYAxis = new FlotAxis("yaxis");
                return mYAxis;
            }
        }
        /// <summary>
        /// Gets a list of x axes when more than one x axis is required
        /// </summary>
        public IList<FlotAxis> XAxes
        {
            get
            {
                if (mXAxes == null)
                    mXAxes = new List<FlotAxis>();
                return mXAxes;
            }
        }
        /// <summary>
        /// Gets a list of y axes when more than one y axis is required
        /// </summary>
        public IList<FlotAxis> YAxes
        {
            get
            {
                if (mYAxes == null)
                    mYAxes = new List<FlotAxis>();
                return mYAxes;
            }
        }

        /// <summary>
        /// Add another Y-axis and return it.
        /// If a .YAxis exists then it is added to the list of Y-axes and then .YAxis is set to null.
        /// Thus either you have one y-axis in .YAxis, or you have more than one y-axis in .YAxes.
        /// </summary>
        /// <returns></returns>
        public FlotAxis AddYaxis()
        {
            if (mYAxes == null)
            {
                mYAxes = new List<FlotAxis>();
            }

            if (mYAxis != null)
            {
                mYAxis.ElementName = null;
                mYAxes.Add(mYAxis);
                mYAxis = null;
            }

            FlotAxis y = new FlotAxis(null);
            mYAxes.Add(y);
            return y;
        }

        /// <summary>
        /// Gets the chart legend
        /// </summary>
        public FlotLegend Legend
        {
            get
            {
                if (mLegend == null)
                    mLegend = new FlotLegend();
                return mLegend;
            }
        }


        public List<FlotColor> Colors
        {
            get
            {
                if (mColors == null)
                    mColors = new List<FlotColor>();
                return mColors;
            }
        }

        /// <summary>
        /// Gets the identifier for this chart.
        /// This is used as the variable name for the client-side instance of this chart
        /// </summary>
        public string ID { get; set; }
        
        /// <summary>
        /// Gets the identifier for the containing DOM element in which the chart is rendered
        /// </summary>
        public string ContainerID { get; set; }
        
        /// <summary>
        /// Gets the chart grid
        /// </summary>
        public FlotGrid Grid
        {
            get
            {
                if (mGrid == null)
                    mGrid = new FlotGrid();
                return mGrid;
            }
        }
        
        /// <summary>
        /// Gets the list of the data series within the chart
        /// </summary>
        public IList<FlotSeries> Series
        {
            get
            {
                if (mSeries == null)
                    mSeries = new List<FlotSeries>();
                return mSeries;
            }
        }
        
        /// <summary>
        /// Creates a new chart series and adds it to the series list
        /// </summary>
        /// <param name="id">A unique identifier for the series</param>
        /// <returns>A new chart series</returns>
        public FlotSeries CreateSeries(string id)
        {
            return CreateSeries(id, string.Empty);
        }
        
        /// <summary>
        /// Creates a new chart series and adds it to the series list
        /// </summary>
        /// <param name="id">A unique identifier for the series</param>
        /// <param name="label">The series label which is displyed on the legend</param>
        /// <returns>A new chart series</returns>
        public FlotSeries CreateSeries(string id, string label)
        {
            FlotSeries s = new FlotSeries()
            {
                ID = id,
                Label = label
            };
            Series.Add(s);
            return s;
        }

        /// <summary>
        /// Creates a new pie chart series and adds it to the series list
        /// </summary>
        /// <param name="id">A unique identifier for the series</param>
        /// <returns>A new chart series</returns>
        public FlotSeries CreateSeriesPie(string id)
        {
            if (Series.Count > 0)
            {
                throw new Exception("A pie chart can only have one series, but this chart already has " + Series.Count + " series.");
            }

            FlotSeries s = CreateSeries(id);
            s.Pie = new FlotPie();

            Legend.Show = false;
            //Grid.Hoverable = false;
            Grid.ShowToolTip = false;

            return s;
        }

        /// <summary>
        /// Creates a new pie chart series and adds it to the series list
        /// </summary>
        /// <param name="id">A unique identifier for the series</param>
        /// <param name="label">The series label which is displyed on the legend</param>
        /// <returns>A new chart series</returns>
        public FlotSeries CreateSeriesPie(string id, string label)
        {
            if (Series.Count > 0)
            {
                throw new Exception("A pie chart can only have one series, but this chart already has " + Series.Count + " series.");
            }
            FlotSeries s = CreateSeries(id, label);
            s.Pie = new FlotPie();

            Legend.Show = false;
            //Grid.Hoverable = false;
            Grid.ShowToolTip = false;
            
            return s;
        }

        /// <summary>
        /// Is this chart a pie chart?
        /// </summary>
        public bool IsPie { get { return mSeries.Count == 1 && mSeries[0].Pie != null; } }

        /// <summary>
        /// Creates a javascript timestamp suitable for a time series
        /// </summary>
        /// <param name="input">The date to convert</param>
        /// <returns>The number of milliseconds since January 1, 1970 00:00:00 UTC</returns>
        public static double GetJavascriptTimestamp(DateTime input)
        {
            TimeSpan span = new TimeSpan(DateTime.Parse("1/1/1970").Ticks);
            DateTime time = input.Subtract(span);
            return (double)(time.Ticks / 10000);
        }
        
        /// <summary>
        /// Returns the client-side Javascript to interact with this chart.
        /// A call to $.plot() function is made to render the chart
        /// </summary>
        /// <returns>The client-side Javascript to interact with this chart.</returns>
        public string Plot()
        {
            return Plot(true);
        }
        
        /// <summary>
        /// Returns the client-side Javascript to interact with this chart.
        /// </summary>
        /// <param name="callPlot">A bool to determine if the returned Javascript contains a call to the $.plot() function to render the chart or not</param>
        /// <returns>The client-side Javascript to interact with this chart.</returns>
        public string Plot(bool callPlot)
        {
            StringBuilder html = new StringBuilder();
            html.AppendLine("<script type=\"text/javascript\">");

            html.AppendLine("var " + ID + " = {");
            html.AppendLine("plot: null,");
            html.AppendLine("containerID: '" + ContainerID + "',");
            html.AppendLine("container: null,");
            html.AppendLine("data: [],");
            html.AppendLine("alreadyFetched: {},");
            html.AppendLine(@"
    addData: function(newData) {  
		var id = newData.id || newData.label;  
		if (id == null || !this.alreadyFetched[id]) {
			if( newData['pie'])
			{
				this.options.series = {};
				this.options.series.pie = newData.pie;
				this.data = newData.data
			}
			else {
				this.data.push(newData);
			}
			if (id != null) { 
				this.alreadyFetched[id] = true; 
			} 
		}
	},"
                );

            if (Grid.ShowToolTip)
            {
                html.AppendLine("previousPoint: null, ");
                html.AppendLine("toolTipContent: " + (string.IsNullOrEmpty(Grid.ToolTipContent) ? "null" : Grid.ToolTipContent) + ", ");
            }

            List<string> options = new List<string>();

            options.Add(Legend.Serialize());
            options.Add(XAxis.Serialize());

            if (mYAxes != null && mYAxes.Count > 0)
            {
                StringBuilder yaxes = new StringBuilder();
                yaxes.AppendLine("yaxes: [");
                foreach (FlotAxis y in mYAxes)
                {
                    yaxes.AppendLine(y.Serialize() + ",");
                }
                yaxes.Remove(yaxes.Length - 2, 1);
                yaxes.AppendLine("]");
                options.Add(yaxes.ToString());
            }
            else
            {
                options.Add(YAxis.Serialize());
            }

            options.Add(Grid.Serialize());

            // some options may not have been set so will be empty
            options.RemoveAll(s => string.IsNullOrEmpty(s));

            string allOptions = string.Join(", ", options.ToArray());
            allOptions += ", colors: ['#103c68','#7a460a','#4e6d29', '#9f2837', '#004f9d', '#a36114', '#76994c', '#c73031', '#0070b9', '#e37c1d', '#89b46e', '#d04840', '#629bd3', '#ff9f34', '#95c36c', '#d35d67', '#93c2e9', '#fcb53e', '#a8cd8d', '#df97a0','#bfdaf2', '#fec357', '#c4dca9', '#e3a4ae']";

            if (!string.IsNullOrEmpty(allOptions))
            {
                html.AppendLine("options: { " + allOptions + " },");
            }

            html.AppendLine("plotData: function(data) { return JQ.plot(this.container, data" + (!string.IsNullOrEmpty(allOptions) ? ", this.options" : string.Empty) + ");" + " },");
            html.AppendLine("plotChart: function() { this.container = JQ('#' + this.containerID); this.plot = this.plotData(this.data);}");

            html.AppendLine("}");

            // Add the data series.
            foreach (FlotSeries s in mSeries)
            {
                html.AppendLine();
                string ser = s.Serialize();
                html.AppendFormat("{0}.addData({1});", ID, ser);
            }
            html.AppendLine();

            // Highlight on hover.
            if (Grid.Hoverable.HasValue && Grid.Hoverable.Value)
            {
                if (IsPie)
                {
                    html.Append(@"
function pieHover(event, pos, obj) {
    if (!obj) {
		JQ('#' + event.currentTarget.id + ' .pieLabel div').css('font-weight', '');
		JQ('#' + event.currentTarget.id + ' .pieLabel').prev('.pieLabelBackground').css('opacity', '0.5');
    }
    else {
		JQ('#' + event.currentTarget.id + ' .pieLabel div').css('font-weight', '');
		JQ('#' + event.currentTarget.id + ' .pieLabel').prev('.pieLabelBackground').css('opacity', '0.5');
		JQ('#' + event.currentTarget.id + ' #pieLabel' + obj.seriesIndex + ' div').css('font-weight', 'bold');
		JQ('#' + event.currentTarget.id + ' #pieLabel' + obj.seriesIndex).prev('.pieLabelBackground').css('opacity', '1');
    }
}
");
                    html.AppendLine("JQ(\"#" + this.ID + "\").bind(\"plothover\", pieHover);");
                }
                else
                {
                    string plothover = @"
JQ('#GraphBlah').bind('plothover', function (event, pos, item) {
        if (item) {
            JQ('#tooltip').remove();
            var x = item.datapoint[0].toFixed(2);
            var y = item.datapoint[1].toFixed(2);
            
            if(item.series.points.show || item.series.lines.show) {
                flotShowTooltip(item.pageX, item.pageY,
                 item.series.label + ': ( ' + x + ', ' + y + ')');
            }
            else if( item.series.bars.show) {
                // setting rotateTicks wipes out the x-axis ticks array.
                if(item.series.xaxis.ticks.length > 0) {
                    flotShowTooltip(item.pageX, item.pageY,
                        item.series.label + '<br />' + item.series.xaxis.ticks[item.dataIndex].label + ': ' + y );
                }
                else {
                    flotShowTooltip(item.pageX, item.pageY, item.series.label + '<br />' + y );
                }
                //flotShowTooltip(item.pageX, item.pageY,
                //    item.series.label + ': ' + y );
            }
        }
        else {
            JQ('#tooltip').remove();
            previousPoint = null;            
        }
    });
";
                    plothover = plothover.Replace("GraphBlah", ID);
                    html.AppendLine(plothover);
                }
            }

            if (callPlot)
            {
                html.AppendLine(ID + ".plotChart();");
            }

            html.Append("</script>");
            return html.ToString();
        }
        
        /// <summary>
        /// Returns the HTML for the container control
        /// </summary>
        /// <returns>The HTML for the container control</returns>
        public string Container()
        {
            return Container();
        }
        
        /// <summary>
        /// Returns the HTML for the container control with the given HTML attributes
        /// </summary>
        /// <param name="htmlAttributes">The HTML attributes to render</param>
        /// <returns>The HTML for the container control</returns>
        public string Container(IDictionary<string, object> htmlAttributes)
        {
            // I am using this instead of TagBuilder so I do not have a
            // reference to MVC
            StringBuilder attributes = new StringBuilder();
            attributes.AppendFormat(AttributeFormat, "id", ContainerID);

            if (!htmlAttributes.ContainsKey("class"))
                attributes.AppendFormat(AttributeFormat, "class", "flot");

            foreach (var entry in htmlAttributes)
            {
                string key = Convert.ToString(entry.Key, CultureInfo.InvariantCulture);
                string value = Convert.ToString(entry.Value, CultureInfo.InvariantCulture);
                attributes.AppendFormat(AttributeFormat, key, value);
            }

            return string.Format(ElementFormatNormal, "div", attributes.ToString(), "");
        }
        
        /// <summary>
        /// Returns the HTML for the container control with the given HTML attributes
        /// </summary>
        /// <param name="htmlAttributes">The HTML attributes to render</param>
        /// <returns>The HTML for the container control</returns>
        public string Container(object htmlAttributes)
        {
            // I am not using RouteValueDictionary
            // so I dont need a reference to System.Web.Routing
            IDictionary<string, object> attributes = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);

            if (htmlAttributes != null)
            {
                foreach (PropertyDescriptor descriptor in TypeDescriptor.GetProperties(htmlAttributes))
                {
                    object obj2 = descriptor.GetValue(htmlAttributes);
                    attributes.Add(descriptor.Name, obj2);
                }
            }

            return Container(attributes);
        }
    }
}